/* eslint-disable node/no-callback-literal */
const { SEARCH_NOVEL, CALLBACK, STOP_SEARCH_NOVEL, SOURCES_LIST, CACHE_CHAPTER_LIST, GEN_MOBI } = require('novel-parser/lib/servies/channel')
const { novel, utils, source } = require('novel-parser')
const { getBookChapter, getNovelChapter } = utils
const fs = require('fs')
const { db } = require('novel-parser')
const path = require('path')
const { promisify } = require('util')
const fetch = require('node-fetch')
const ejs = require('ejs')
const { spawn } = require('child_process')
const log = console
const rimraf = require('rimraf')
const mkdirp = require('mkdirp')
const os = require('os')

const { getBookDir, getChapterId } = require('./index')

const __static = path.resolve(__dirname, '../public/static')

const callback = (socket, type, payload) => socket.send(JSON.stringify({ type, payload }))

const services = {
  [SEARCH_NOVEL]: async (socket, arg) => {
    const { searchKey } = arg
    const sources = await source[SOURCES_LIST]()

    const iter = sources[Symbol.iterator]()

    let tasks = []

    const gen = async () => {
      if (tasks.length >= 5) {
        await Promise.race(tasks)
      }
      const next = iter.next()
      if (!next.done && !socket.stop) {
        const source = next.value
        const p = novel[SEARCH_NOVEL](source, searchKey)
        p.then(result => {
          tasks = tasks.filter(pending => pending !== p)
          const res = (result || []).filter(x => !!x.name).map(x => ({ ...x, sourceId: source._id }))
          if (res.length > 0) {
            callback(socket, { type: SEARCH_NOVEL + CALLBACK, payload: { code: 200, data: res } })
          }
        }).catch(e => callback(socket, { type: SEARCH_NOVEL + CALLBACK, payload: utils.formatError(e, source) }))
          .finally(() => gen())
      }
    }
    gen()
  },
  [STOP_SEARCH_NOVEL]: async (socket, payload) => {
    socket.stop = true
    callback(socket, {
      type: STOP_SEARCH_NOVEL + CALLBACK,
      payload: {
        code: 200
      }
    })
  },
  [CACHE_CHAPTER_LIST]: async (socket, payload) => {
    const { novelId, force } = payload
    const result = await db.novels().findOne({ _id: novelId })
    const chapters = result.chapters.filter(chapter => {
      return force || !fs.existsSync(getBookChapter(result._id, chapter.chapterUrl))
    })

    const iter = chapters[Symbol.iterator]()
    let tasks = []

    const gen = async () => {
      if (tasks.length >= 1) {
        await Promise.race(tasks)
      }
      const next = iter.next()
      if (!next.done) {
        const chapter = next.value
        const p = getNovelChapter(result, chapter.chapterUrl, force)
        p.then(result => {
          tasks = tasks.filter(pending => pending !== p)
          if (result) {
            callback(socket, CACHE_CHAPTER_LIST + CALLBACK, { code: 200, data: { status: 'item-done', item: chapter } })
          } else {
            callback(socket, CACHE_CHAPTER_LIST + CALLBACK, { code: 200, data: { status: 'item-empty', item: chapter } })
          }
        }).catch(e => callback(socket, CACHE_CHAPTER_LIST + CALLBACK, utils.formatError(e, source)))
          .finally(() => gen())
      } else {
        callback(socket, CACHE_CHAPTER_LIST + CALLBACK, { code: 200, data: { status: 'done' } })
      }
    }
    gen()
  },
  [GEN_MOBI]: async (socket, arg) => {
    const { novelId } = arg
    let kindlegen
    if (process.platform === 'win32') {
      kindlegen = path.resolve(__static, '/bin/kindlegen.exe')
    } else {
      kindlegen = path.resolve(__static, '/bin/kindlegen')
    }
    const bookdir = getBookDir(novelId)
    const tempBookdir = path.join(os.tmpdir(), novelId)

    rimraf.sync(tempBookdir)
    fs.mkdirSync(tempBookdir)

    if (!fs.existsSync(bookdir)) {
      throw new Error('请确认小说已缓存')
    }

    const bookInfo = await db.novels().findOne({ _id: novelId })

    if (!bookInfo) {
      throw new Error('未找到该小说')
    }

    if (bookInfo.coverUrl) {
      const res = await fetch(bookInfo.coverUrl).then(res => res.buffer())
      await promisify(fs.writeFile)(path.join(tempBookdir, 'cover.jpg'), res)
    }
    const chapters = bookInfo.chapters

    const newChapters = []

    for (const chapter of chapters) {
      const fileName = getChapterId(chapter.chapterUrl)

      newChapters.push({
        title: chapter.chapterName,
        href: fileName,
        id: fileName.split('.')[0]
      })
      const html = fs.readFileSync(getBookChapter(novelId, chapter.chapterUrl), { encoding: 'utf8' })
      try {
        const chapterHtml = await ejs.renderFile(path.join(__static, 'template/content.html'), {
          title: chapter.chapterName,
          content: html
        }, { async: true })
        fs.writeFileSync(path.join(tempBookdir, fileName), chapterHtml)
      } catch (e) {
        console.log(e)
      }
    }

    const opf = await ejs.renderFile(path.join(__static, 'template/content.opf'), {
      chapters: newChapters,
      title: bookInfo.name,
      author: bookInfo.author,
      date: new Date(),
      description: bookInfo.introduce
    }, { async: true })
    fs.writeFileSync(path.join(tempBookdir, 'content.opf'), opf)
    const ncx = await ejs.renderFile(path.join(__static, 'template/toc.ncx'), {
      chapters: newChapters,
      title: bookInfo.name,
      author: bookInfo.author
    }, { async: true })
    fs.writeFileSync(path.join(tempBookdir, 'toc.ncx'), ncx)

    if (!fs.existsSync(kindlegen)) {
      throw new Error('请确认kindlegen是否存在，点击右上角配置。')
    }

    return new Promise((resolve, reject) => {
      const ps = spawn(kindlegen, [path.join(tempBookdir, 'content.opf')])
      ps.stdout.on('data', (data) => {
        log.info(data.toString('utf8'))
        callback(socket, GEN_MOBI + CALLBACK, { code: 200, message: data.toString('utf8'), data: 'pending' })
      })

      ps.stderr.on('data', (data) => {
        log.error(data.toString('utf8'))
        callback(socket, GEN_MOBI + CALLBACK, utils.formatError(new Error(data.toString('utf8'))))
      })

      ps.on('close', async (code) => {
        if (fs.existsSync(path.join(tempBookdir, 'content.mobi'))) {
          const staticBookDir = path.resolve(__static, 'book')
          await mkdirp(staticBookDir)
          const bookDir = path.join(staticBookDir, `${bookInfo.name}.mobi`)
          fs.copyFileSync(path.join(tempBookdir, 'content.mobi'), bookDir)
          callback(socket, GEN_MOBI + CALLBACK, { code: 200, message: '', data: 'done' })
          await db.novels().update({ _id: novelId }, { $set: { mobi: `/static/book/${bookInfo.name}.mobi`, mobiUpdate: new Date() } })
          resolve(bookDir)
        } else {
          reject(code)
        }
      })
    })
  }
}

function handler (socket, obj) {
  if (services[obj.type]) {
    services[obj.type](socket, obj.payload)
  } else {
    console.error('unkown type ' + obj.type)
  }
}

module.exports = {
  handler
}
